/**
 * Captain
 * © 2017 Captain copyright，All rights reserved.
 * http://www.sccaptain.com.cn
 * 
 * JAVA : 8
 * 文  件  名: CasServiceAuthenticationDetails.java
 * 创  建  人: Roc Zheng (roc.djz@gmail.com)
 * 创建时间: 2017年8月23日 上午11:28:55
 * 版         本: 1.0.0
 * 备         注:
 * 修订历史:
 */
package com.material.c2c.web.sys.security;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;

/**
 * 
 * 
 * @since 1.0
 * @version 1.0
 * @author Roc Zheng (roc.djz@gmail.com)
 */
public class CasServiceAuthenticationDetails extends WebAuthenticationDetails implements ServiceAuthenticationDetails {
  private static final long serialVersionUID = 1L;
  
  private final String serviceUrl;

  /**
   * Creates a new instance
   * @param request the current {@link HttpServletRequest} to obtain the
   * {@link #getServiceUrl()} from.
   * @param artifactPattern the {@link Pattern} that will be used to clean up the query
   * string from containing the artifact name and value. This can be created using
   * {@link #createArtifactPattern(String)}.
   */
  public CasServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern) throws MalformedURLException {
    super(request);
    URL casServiceUrl = new URL(casService);
    int port = getServicePort(casServiceUrl);
    final String query = getQueryString(request, artifactPattern);
    this.serviceUrl = UrlUtils.buildFullRequestUrl(casServiceUrl.getProtocol(),
        casServiceUrl.getHost(), port, request.getRequestURI(), query);
  }
  /**
   * Returns the current URL minus the artifact parameter and its value, if present.
   * @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
   */
  public String getServiceUrl() {
    return serviceUrl;
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = super.hashCode();
    result = prime * result + serviceUrl.hashCode();
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!super.equals(obj) || !(obj instanceof CasServiceAuthenticationDetails)) {
      return false;
    }
    ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
    return serviceUrl.equals(that.getServiceUrl());
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    result.append(super.toString());
    result.append("ServiceUrl: ");
    result.append(serviceUrl);
    return result.toString();
  }

  /**
   * If present, removes the artifactParameterName and the corresponding value from the
   * query String.
   * @param request
   * @return the query String minus the artifactParameterName and the corresponding
   * value.
   */
  private String getQueryString(final HttpServletRequest request,
      final Pattern artifactPattern) {
    final String query = request.getQueryString();
    if (query == null) {
      return null;
    }
    final String result = artifactPattern.matcher(query).replaceFirst("");
    if (result.length() == 0) {
      return null;
    }
    // strip off the trailing & only if the artifact was the first query param
    return result.startsWith("&") ? result.substring(1) : result;
  }

  /**
   * Creates a {@link Pattern} that can be passed into the constructor. This allows the
   * {@link Pattern} to be reused for every instance of
   * {@link DefaultServiceAuthenticationDetails}.
   *
   * @param artifactParameterName
   * @return
   */
  static Pattern createArtifactPattern(String artifactParameterName) {
    Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length");
    return Pattern.compile("&?" + Pattern.quote(artifactParameterName) + "=[^&]*");
  }

  /**
   * Gets the port from the casServiceURL ensuring to return the proper value if the
   * default port is being used.
   * @param casServiceUrl the casServerUrl to be used (i.e.
   * "https://example.com/context/login/cas")
   * @return the port that is configured for the casServerUrl
   */
  private static int getServicePort(URL casServiceUrl) {
    int port = casServiceUrl.getPort();
    if (port == -1) {
      port = casServiceUrl.getDefaultPort();
    }
    return port;
  }
}
